/**
    @file pixelmap.cpp
    @brief Game map
*/
#include "pixel/pixelmap.hpp"
#include "lua/lua_ogre.hpp"
#include "lua/lua_pixelmap.hpp"

pm3::PIXELMAP *pm3::lua::g_active_map = NULL;

pm3::PIXELMAP::PIXELMAP() {
    m_id = -1;

    m_size = 10.0;

    m_render_mode = pm3::RM_PVS;
    m_show_debug = false;
}

pm3::PIXELMAP::~PIXELMAP() {
    // Let PCZSceneManager deal with the zones
    m_zones.clear();
    // Unload the rest
    unload();
}

pm3::PIXELMAP& pm3::PIXELMAP::operator=( const pm3::PIXELMAP &r_map ) {
    m_id = r_map.m_id;
    m_name = r_map.m_name;
    m_size = r_map.m_size;

    m_layers = r_map.m_layers;
    m_zones = r_map.m_zones;
    m_objects = r_map.m_objects;
    m_colormap = r_map.m_colormap;
    m_colortags = r_map.m_colortags;
    m_startpoints = r_map.m_startpoints;

    return *this;
}

void pm3::PIXELMAP::bind() {
    tolua_ogre_colourvalue_open( m_lua_state );
    tolua_ogre_scenemanager_open( m_lua_state );
    tolua_mapscript_open( m_lua_state );
}

void pm3::PIXELMAP::unload() {
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    std::set<PIXELOBJ *, OBJ_COMPARE>::iterator it;
    size_t i;

    m_id = -1;
    m_name = "";

    for (i=0; i<m_layers.size(); i++)
        delete m_layers [i];
    m_layers.clear();

    it = m_objects.begin();
    while (it != m_objects.end()) {
        delete *it;
        it++;
    }
    m_objects.clear();

    // Destroy all zones along with their content
    if (intf && intf->get_scenemgr()) {
        for (i=0; i<m_zones.size(); i++) {
            if (m_zones [i])
                intf->get_scenemgr()->destroyZone( m_zones [i], true );
        }
    }
    m_zones.clear();

    m_colormap.clear();
    m_colortags.clear();
    m_startpoints.clear();
}

std::vector<Ogre::Vector3> pm3::PIXELMAP::find_path() const {
    std::vector<Ogre::Vector3> path;

    //! \todo Implement wave-iterated pathfinder to the closest finish

    return path;
}

Ogre::ViewPoint pm3::PIXELMAP::find_start() const {
    LOGGER *log = LOGGER::getSingletonPtr();
    Ogre::ViewPoint point;
    int startpoint = 0;

    // Get a random startpoint
    if (!m_startpoints.empty()) {
        startpoint = rand() % m_startpoints.size();
        if (log)
            log->report( "Pixel", "I: Picked startingpoint %d\n", startpoint );
        return m_startpoints [startpoint];
    }

    if (log)
        log->report( "Pixel", "E: Couldn't find any startpoints on map %d (\"%s\")\n", m_id, m_name.c_str() );
    return point;
}

Ogre::ViewPoint pm3::PIXELMAP::get_start( int i_id ) const {
    LOGGER *log = LOGGER::getSingletonPtr();
    Ogre::ViewPoint point;

    if (i_id >= 0 && i_id < (int) m_startpoints.size())
        return m_startpoints [i_id];

    if (log)
        log->report( "Pixel", "E: Couldn't find any startpoints on map %d (\"%s\")\n", m_id, m_name.c_str() );
    return point;
}

std::string pm3::PIXELMAP::get_tags( int i_layer, int i_x, int i_y ) {
//    LOGGER *log = LOGGER::getSingletonPtr();
    std::map<Ogre::RGBA, std::string>::iterator it;
    Ogre::ColourValue color;
    PIXELOBJ *obj = NULL;

    // Get the room pointer
    PIXELROOM *room = get_room( i_layer, i_x, i_y );
    // Is there a room?
    if (room) {
        // Get room object pointer
        obj = room->get_obj();
        // Is the room initialized (is the object valid)?
        if (obj) {
            return obj->get_tags();
        } else {
            // Fetch the color
            color = m_layers [i_layer]->get_pixel( i_x, i_y );

//            if (log)
//                log->report( "Pixel", "I: Getting tags of (%d, %d) on layer %d (color 0x%X)\n", i_x, i_y, i_layer, 
//                    color.getAsRGBA() );

            // See if we can find the tags
            it = m_colortags.find( color.getAsRGBA() );
            // We found it!
            if (it != m_colortags.end())
                return it->second;
        }
    }

    return "";
}

std::string pm3::PIXELMAP::get_tags( const Ogre::ColourValue &r_col ) {
    std::map<Ogre::RGBA, std::string>::iterator it;

    // See if we can find the tags
    it = m_colortags.find( r_col.getAsRGBA() );
    // We found it!
    if (it != m_colortags.end())
        return it->second;

    return "";
}

bool pm3::PIXELMAP::check_tag( int i_layer, int i_x, int i_y, const std::string &r_tag ) {
    return pm3::util::has_tag( get_tags( i_layer, i_x, i_y ), r_tag );
}

int pm3::PIXELMAP::get_directions( int i_layer, int i_x, int i_y ) {
//    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELOBJ *obj = NULL;
    int directions = 0;

    // Get the room pointer
    PIXELROOM *room = get_room( i_layer, i_x, i_y );
    // Is there a room?
    if (room) {
        // Get room object pointer
        obj = room->get_obj();
        // Is the room initialized (is the object valid)?
        if (obj) {
            directions = obj->get_directions();
        } else {
//            if (log)
//                log->report( "Pixel", "I: Getting directions of (%d, %d) on layer %d\n", i_x, i_y, i_layer );

            // Should this room be ignored?
            if (check_tag( i_layer, i_x, i_y, OT_PASSAGE )) {
                // Scout to the north
                if (check_tag( i_layer, i_x, i_y - 1, OT_PASSAGE ))
                    directions |= pm3::util::D_N;
                // Scout to the east
                if (check_tag( i_layer, i_x + 1, i_y, OT_PASSAGE ))
                    directions |= pm3::util::D_E;
                // Scout to the south
                if (check_tag( i_layer, i_x, i_y + 1, OT_PASSAGE ))
                    directions |= pm3::util::D_S;
                // Scout to the west
                if (check_tag( i_layer, i_x - 1, i_y, OT_PASSAGE ))
                    directions |= pm3::util::D_W;
                // Scout up
                if (check_tag( i_layer + 1, i_x, i_y, OT_PASSAGE ))
                    directions |= pm3::util::D_U;
                // Scout down
                if (check_tag( i_layer - 1, i_x, i_y, OT_PASSAGE ))
                    directions |= pm3::util::D_D;
            }
        }
    }

    return directions;
}

pm3::PIXELROOM *pm3::PIXELMAP::get_neighbour( int i_layer, int i_x, int i_y, int i_dir ) {
    if ( ( i_dir & pm3::util::D_N ) != 0)
        return get_room( i_layer, i_x, i_y - 1 );
    if ( ( i_dir & pm3::util::D_E ) != 0)
        return get_room( i_layer, i_x + 1, i_y );
    if ( ( i_dir & pm3::util::D_S ) != 0)
        return get_room( i_layer, i_x, i_y + 1 );
    if ( ( i_dir & pm3::util::D_W ) != 0)
        return get_room( i_layer, i_x - 1, i_y );
    if ( ( i_dir & pm3::util::D_U ) != 0)
        return get_room( i_layer + 1, i_x, i_y );
    if ( ( i_dir & pm3::util::D_D ) != 0)
        return get_room( i_layer - 1, i_x, i_y );
    return NULL;
}

pm3::PIXELOBJ *pm3::PIXELMAP::get_obj( int i_id ) const {
    std::set<PIXELOBJ *, OBJ_COMPARE>::iterator it;

    if (i_id < 0 || i_id > (int) m_objects.size())
        return NULL;

    it = m_objects.begin();
    while (it != m_objects.end()) {
        if (i_id == 0)
            return *it;

        it++;
        i_id--;
    }

    return NULL;
}

pm3::PIXELOBJ *pm3::PIXELMAP::get_obj( int i_layer, int i_x, int i_y ) {
//    LOGGER *log = LOGGER::getSingletonPtr();
    std::map<PIXEL, PIXELOBJ *>::iterator it;
    PIXELOBJ *obj = NULL;

    // Get the room pointer
    PIXELROOM *room = get_room( i_layer, i_x, i_y );
    
    // Is there a room?
    if (room) {
        // Get room object pointer
        obj = room->get_obj();
        // Is room object pointer already set?
        if (obj) {
            return obj;
        } else {
            PIXEL pix;
            // Fetch the color
            pix.m_color = m_layers [i_layer]->get_pixel( i_x, i_y );
            // Fetch the directions
            pix.m_directions = get_directions( i_layer, i_x, i_y );

/*            if (log)
                log->report( "Pixel", "I: Room at (%d, %d) has color (%f, %f, %f, %f) and directions \"%s\"\n",
                    i_x, i_y, pix.m_color.r, pix.m_color.g, pix.m_color.b, pix.m_color.a,
                    pm3::util::directions_to_str( pix.m_directions ).c_str() );*/

            // Let's see if there's a matching object
            it = m_colormap.find( pix );
            // Found it?
            if (it != m_colormap.end())
                return it->second;
        }
    }

    return NULL;
}

pm3::PIXELOBJ *pm3::PIXELMAP::get_obj( const Ogre::ColourValue &r_col, int i_dirs ) {
    std::map<PIXEL, PIXELOBJ *>::iterator it;
    PIXEL pix;

    pix.m_color = r_col;
    pix.m_directions = i_dirs;

    // Let's see if there's a matching object
    it = m_colormap.find( pix );
    // Found it?
    if (it != m_colormap.end())
        return it->second;

    return NULL;
}

pm3::PIXELROOM *pm3::PIXELMAP::get_room( int i_layer, int i_x, int i_y ) {
    LOGGER *log = LOGGER::getSingletonPtr();

    // Out of bounds?
    if (i_layer < 0 || i_layer >= (int) m_layers.size())
        return NULL;

    if (!m_layers [i_layer]) {
        if (log)
            log->report("Pixel", "E: Layer %d has a NULL pointer\n", i_layer);
        return NULL;
    }

    // Get the room pointer
    return m_layers [i_layer]->get_room( i_x, i_y );
}

Ogre::PCZone *pm3::PIXELMAP::get_zone( int i_layer, int i_x, int i_y ) {
    PIXELROOM *room = get_room( i_layer, i_x, i_y );

    if (room)
        return room->get_zone();
    return NULL;
}

pm3::PIXELLAYER *pm3::PIXELMAP::get_layer( int i_id ) const {
    if (i_id < 0 || i_id > (int) m_layers.size())
        return NULL;
    return m_layers [i_id];
}

pm3::PIXELLAYER *pm3::PIXELMAP::get_layer( const Ogre::Vector3 &r_point ) const {
    Ogre::Vector3 v = world_to_grid( r_point );
    int y = (int) v.y;

    if (m_layers.empty())
        return NULL;

    if (v.y < 0)
        y = 0;
    if (v.y >= m_layers.size())
        y = m_layers.size() - 1;

    return m_layers [y];
}

Ogre::Vector3 pm3::PIXELMAP::grid_to_world( const Ogre::Vector3 &r_grid ) const {
    return r_grid * m_size;
}

Ogre::Vector3 pm3::PIXELMAP::world_to_grid( const Ogre::Vector3 &r_world ) const {
    return pm3::util::round( r_world / m_size );
}

bool pm3::PIXELMAP::room_setup( PIXELROOM *p_room ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    Ogre::ViewPoint viewpoint;
    int direction = 0;

    if (!p_room)
        return false;

    Ogre::Vector3 v = p_room->get_grid_pos();

    // Get a pointer to the object that should correspond to the pixel
    PIXELOBJ *obj = get_obj( v.y, v.x, v.z );

    if (obj) {
        //! \todo Make sure colortags don't accumulate when not necessary
        // Add colortags
        obj->add_tags( get_tags( v.y, v.x, v.z ) );

        if (log)
            log->report( "Pixel", "I: Room (%d, %d) has tags \"%s\"\n", (int) v.x, (int) v.z, obj->get_tags().c_str() );

        // Perhaps this room is also a startingpoint?
        if (pm3::util::has_tag( obj->get_tags(), OT_START )) {

            if (log)
                log->report( "Pixel", "I: Room (%d, %d) is marked as a startpoint\n", (int) v.x, (int) v.z );

            // Set the startingpoint position
            viewpoint.position = v;
            // Find a random direction (of all possibilities) for the startingpoint
            direction = pm3::util::get_rand_direction( obj->get_directions() );
            if (direction != pm3::util::D_NONE) {
                // Calculate a quaternion based on the direction
                viewpoint.orientation = pm3::util::direction_to_quat( direction );
                // Add the startpoint
                add_startpoint( viewpoint );
            }
        } else {
            // If it was a startpoint earlier, then the startingpoint should be removed
            remove_startpoint( v );
        }

        if (log)
            log->report( "Pixel", "I: Initializing room (%d, %d)\n", (int) v.x, (int) v.z );

        // Initialize the room
        p_room->init( m_size, obj );

        if (log)
            log->report( "Pixel", "I: Setting up object instance for room (%d, %d)\n", (int) v.x, (int) v.z );

        // Call the corresponding objectscript for a scene setup
        obj->instance_setup( (PIXELNODE *) p_room, v );

        if (log)
            log->report( "Pixel", "I: Setting up physics for room (%d, %d)\n", (int) v.x, (int) v.z );

        // Setup physics
        p_room->post_setup();

        return true;
    } else {
        // If it was a startpoint earlier, then the startingpoint should be removed
        remove_startpoint( v );

        if (log)
            log->report( "Pixel", "I: Unloading room.\n" );

        // Unload the room
        p_room->unload();

        // Hide room
//        p_room->set_render_mode( pm3::RM_HIDE );

        return true;
    }
    return false;
}

void pm3::PIXELMAP::setup_layer( int i_layer ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELROOM *room = NULL;
//    PIXELOBJ *obj = NULL;
    int width, height, x0, y0;

    if (log)
        log->report("Pixel", "I: Setting up Layer %d\n", i_layer );

    // Out of bounds?
    if (i_layer < 0 || i_layer >= (int) m_layers.size()) {
        if (log)
            log->report("Pixel", "E: Layer %d is out of bounds (0..%d)\n", i_layer, m_layers.size());
        return;
    }

    if (!m_layers [i_layer]) {
        if (log)
            log->report("Pixel", "E: Layer %d has a NULL pointer\n", i_layer);
        return;
    }

    // Get layer coordinate offsets
    x0 = m_layers [i_layer]->get_x_offset();
    y0 = m_layers [i_layer]->get_y_offset();
    // Get layer dimensions
    width = m_layers [i_layer]->get_width();
    height = m_layers [i_layer]->get_height();

    if (log)
        log->report("Pixel", "I: Layer dimensions (%d, %d) with offset (%d, %d)\n", 
                    width, height, x0, y0 );

    // Loop through the whole layer
    for (int y=0; y<height; y++) {
        for (int x=0; x<width; x++) {
            // Get the roompointer
            room = m_layers [i_layer]->get_room( x - x0, y - y0 );
            // Is there a room?
            if (room) {
                room_setup( room );
            } else {
                if (log)
                    log->report( "Pixel", "E: Pixel (%d, %d) has no room allocated - probably a problem with layer coordinate offsets\n", 
                                x - x0, y - y0 );
            }
        }
    }
}

Ogre::PCZone *pm3::PIXELMAP::room_zone_setup( PIXELROOM *p_room ) {
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    LOGGER *log = LOGGER::getSingletonPtr();
    Ogre::PCZone *zone = NULL;
    std::string name;

    // Do we have an interface to scenemanager?
    if (!intf || !intf->get_scenemgr()) {
        if (log)
            log->report( "Pixel", "E: No interface to scenemanager, needed for initializing room portals\n" );
        return NULL;
    }

    if (!p_room || !p_room->get_node())
        return NULL;

    Ogre::Vector3 v = p_room->get_grid_pos();

    // Does the room already have a zone?
    zone = p_room->get_zone();
    if (!zone) {
        //! \todo Make sure that room coordinates are in the same order everywhere

        //! \todo Make up better zone names

        // Make up a name for the zone
        name = std::string( "Zone_" ) + pm3::util::to_str( m_zones.size() );

        if (log)
            log->report( "Pixel", "I: Initializing portals for room %d, %d, %d (in zone \"%s\")\n", 
                            (int) v.y, (int) v.x, (int) v.z, name.c_str() );

        //! \todo Optimize zones by larger corridors and open areas
        zone = intf->get_scenemgr()->createZone( "ZoneType_Default", name );

        //! \todo Create a node that would encompass all the nodes optimized into a zone

        // Set enclosure node
        zone->setEnclosureNode( p_room->get_node() );
        // And attach it to the scene manager
        intf->get_scenemgr()->addPCZSceneNode( p_room->get_node(), zone );

        // Add the zone
        m_zones.push_back( zone );
    }

    // Set up room portals
    p_room->init_portals( zone );

    // Setup room rendering mode
    p_room->set_render_mode( get_layer( v )->get_render_mode() );
    // Setup room debug rendering mode
    p_room->show_debug_box( get_layer( v )->debug_boxes_shown() );

    return zone;
}

bool pm3::PIXELMAP::room_pair_portals( PIXELROOM *p_room ) {
//    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELROOM *neighbour = NULL;
    PIXELOBJ *obj = NULL;
    int dirs = 0, rev_dir = 0;

    if (!p_room)
        return false;

    Ogre::Vector3 v = p_room->get_grid_pos();

    // Get object pointer
    obj = get_obj( v.y, v.x, v.z );
    // Is there such an object?
    if (obj) {
        // Get object directions
        dirs = obj->get_directions();

//        if (log)
//            log->report( "Pixel", "I: Room %d, %d, %d with zone \"%s\" has directions \"%s\"\n", 
//                            y, x, z, room->get_zone()->getName().c_str(), pm3::util::directions_to_str( dirs ).c_str() );

        // Walk them through and pair portals
        for (int i=1; i<=pm3::util::D_LAST; i<<=1) {
            if (( dirs & i ) != 0) {
                // Does the room have the corresponding portal and zone initialized?
                if (p_room->get_portal( i ) && p_room->get_zone()) {
                    // Get a pointer to the room in the given direction
                    neighbour = get_neighbour( v.y, v.x, v.z, i );
                    // Get reverse direction for the other portal
                    rev_dir = pm3::util::reverse_directions( i );

                    // Is there such a neighbour (needs to have the portal initialized)?
                    if (neighbour && neighbour->get_portal( rev_dir ) && neighbour->get_zone()) {
//                        if (log)
//                            log->report( "Pixel", "I: Neighbour in dir \"%s\" has zone \"%s\"\n", 
//                                            pm3::util::directions_to_str( i ).c_str(), neighbour->get_zone()->getName().c_str() );

                        // Set portal target zones
                        if ( neighbour->get_portal( rev_dir )->getTargetZone() != p_room->get_zone() )
                            neighbour->get_portal( rev_dir )->setTargetZone( p_room->get_zone() );
                        if ( p_room->get_portal( i )->getTargetZone() != neighbour->get_zone() )
                            p_room->get_portal( i )->setTargetZone( neighbour->get_zone() );

                        // Connect the portals
                        if ( neighbour->get_portal( rev_dir )->getTargetPortal() != p_room->get_portal( i ) )
                            neighbour->get_portal( rev_dir )->setTargetPortal( p_room->get_portal( i ) );
                        if ( p_room->get_portal( i )->getTargetPortal() != neighbour->get_portal( rev_dir ) )
                            p_room->get_portal( i )->setTargetPortal( neighbour->get_portal( rev_dir ) );
                    } else {
                        // Remove the portals
                        neighbour->remove_portal( rev_dir );
                        p_room->remove_portal( i );
                    }
                }
            }
        }

        return true;
    }

    return false;
}

void pm3::PIXELMAP::setup_portals() {
    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELROOM *room = NULL;
    PIXELOBJ *obj = NULL;
    int width, height, x0, z0;
    int x = 0, y = 0, z = 0;

    if (log)
        log->report( "Pixel", "I: Setting up portals\n" );

    // Create zones and init room portals
    for (y=0; y<(int) m_layers.size(); y++) {
        if (!m_layers [y])
            continue;

        // Get layer coordinate offsets
        x0 = m_layers [y]->get_x_offset();
        z0 = m_layers [y]->get_y_offset();

        width = m_layers [y]->get_width();
        height = m_layers [y]->get_height();

        for (x=0; x<width; x++) {
            for (z=0; z<height; z++) {
                // Get the roompointer
                room = get_room( y, x - x0, z - z0 );
                // Get object pointer
                obj = get_obj( y, x - x0, z - z0 );
                // Is there a room with an object inside?
                if (room && room->get_node() && obj) {
                    room_zone_setup( room );
                }
            }
        }
    }

    for (y=0; y<(int) m_layers.size(); y++) {
        if (!m_layers [y])
            continue;

        // Get layer coordinate offsets
        x0 = m_layers [y]->get_x_offset();
        z0 = m_layers [y]->get_y_offset();

        width = m_layers [y]->get_width();
        height = m_layers [y]->get_height();

        for (x=0; x<width; x++) {
            for (z=0; z<height; z++) {
                // Get the roompointer
                room = get_room( y, x - x0, z - z0 );
                // And pair room portals
                room_pair_portals( room );
            }
        }
    }
}

void pm3::PIXELMAP::update( const Ogre::Vector3 &r_player_grid_pos ) {
//    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELOBJ *obj = NULL;
    std::string tags;

    // Set the active map
    pm3::lua::g_active_map = this;
    // Set the active node
    pm3::lua::g_active_node = (pm3::PIXELNODE *) get_room( r_player_grid_pos.y, r_player_grid_pos.x, r_player_grid_pos.z );

    // Proceed with the default luascript update
    pm3::LUA_SCRIPTED::update();

    // Has player switched rooms?
    if (m_old_player_grid_pos != r_player_grid_pos) {
        // Update the stored player grid position
        m_old_player_grid_pos = r_player_grid_pos;
        // Get the object of the room that the player is in
        obj = get_obj( r_player_grid_pos.y, r_player_grid_pos.x, r_player_grid_pos.z );
        // Call the enter callback from the objectscript
        if (obj) {
            // Is it a finish?
            if (pm3::util::has_tag( obj->get_tags(), OT_FINISH )) {
                // Post a message for mapswitch to the pixelworld
                pm3::g_mail.getSingleton().post( PIXELPOST( PM_NEXT_MAP ) );
            }
            // Execute any luascript callbacks
            obj->enter_room( r_player_grid_pos.y, r_player_grid_pos.x, r_player_grid_pos.z );
        }
    }
}

void pm3::PIXELMAP::init() {
    // Initialize all layers
    for (size_t i=0; i<m_layers.size(); i++)
        setup_layer( i );
    // Initialize portals
    setup_portals();
}

void pm3::PIXELMAP::debug_render() {
    int x, y, z, width, height, x0, z0;
    PIXELROOM *room = NULL;

    for (y=0; y<(int) m_layers.size(); y++) {
        if (!m_layers [y])
            continue;

        // Get layer coordinate offsets
        x0 = m_layers [y]->get_x_offset();
        z0 = m_layers [y]->get_y_offset();

        width = m_layers [y]->get_width();
        height = m_layers [y]->get_height();

        for (x=0; x<width; x++) {
            for (z=0; z<height; z++) {
                room = get_room( y, x - x0, z - z0 );

                if (room)
                    room->debug_render();
            }
        }
    }
}

int pm3::PIXELMAP::add_layer( const std::string &r_path ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELLAYER *layer = NULL;
    std::string path;
    int ret = 0;

    // Add the layer
    layer = new PIXELLAYER();
    m_layers.push_back( layer );

    // Mark the layer as active
    pm3::lua::g_active_layer = m_layers [m_layers.size() - 1];

    // Get the full path
    path = get_directory() + r_path;

    // Load the layer
    if (m_layers [m_layers.size() - 1]->load( path )) {
        // Initialize the layer
        ret = m_layers.size() - 1;
        m_layers [m_layers.size() - 1]->init( ret );

        // Setup layer render mode
        m_layers [m_layers.size() - 1]->set_render_mode( get_render_mode() );
        // Setup layer debug render mode
        m_layers [m_layers.size() - 1]->show_debug_boxes( debug_boxes_shown() );

        // Mark the map as unsaved
        m_unsaved = true;
    } else {
        if (log)
            log->report( "Pixel", "E: Failed to load layer \"%s\"\n", path.c_str() );
    }

    // Return Number of layers
    return ret;
}

int pm3::PIXELMAP::new_layer() {
//    LOGGER *log = LOGGER::getSingletonPtr();
    PIXELLAYER *layer = NULL;
    std::string path;
    int ret = 0;

    // Add the layer
    layer = new PIXELLAYER();
    m_layers.push_back( layer );

    // Mark the layer as active
    ret = m_layers.size() - 1;
    pm3::lua::g_active_layer = m_layers [ret];
    // Initialize the layer
    m_layers [m_layers.size() - 1]->init( ret );

    // Setup layer render mode
    m_layers [m_layers.size() - 1]->set_render_mode( get_render_mode() );
    // Setup layer debug render mode
    m_layers [m_layers.size() - 1]->show_debug_boxes( debug_boxes_shown() );

    // Mark the map as unsaved
    m_unsaved = true;

    // Return Number of layers
    return ret;
}

int pm3::PIXELMAP::add_object( const Ogre::ColourValue &r_color, const std::string &r_dirs, const std::string &r_path ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    std::pair< std::set<PIXELOBJ *, OBJ_COMPARE>::iterator, bool > pair;
    Ogre::ViewPoint viewpoint;
    pm3::PIXELMAP::PIXEL pix;
    pm3::PIXELOBJ *obj = NULL;
    std::string path;
    int ret = 0;

    // Initialize pixel
    pix.m_color = r_color;
    pix.m_directions = pm3::util::str_to_directions( std::string( r_dirs ) );
    // Normalize color values
    if (pix.m_color.a > 1.0f)
        pix.m_color /= 255.0f;

    // Add the object
    obj = new PIXELOBJ();

    obj->m_color = pix.m_color;
    obj->m_directions = pix.m_directions;

    pair = m_objects.insert( obj );

    // Such an object already exists?
    if (!pair.second) {
        // Change the existing one then..
        delete obj;
        obj = *pair.first;
    }

    // Map the pixel
    m_colormap [pix] = obj;

    // Get real objectscript path (if r_path already isn't the real path)
    if (!pm3::util::has_dir( r_path, get_directory() ))
        path = get_directory() + r_path;
    else
        path = r_path;

    // Load the object
    pm3::lua::g_active_obj = obj;
    if (obj->load( path )) {
        // Get element index
        ret = std::distance( m_objects.begin(), pair.first );

        // Mark the map as unsaved
        m_unsaved = true;
    } else {
        if (log)
            log->report( "Pixel", "E: Failed to load object \"%s\"\n", path.c_str() );
    }

    return ret;
}

int pm3::PIXELMAP::add_color( const Ogre::ColourValue &r_color, const std::string &r_tags ) {
    std::pair< std::map< Ogre::RGBA, std::string >::iterator, bool > pair;
//    LOGGER *log = LOGGER::getSingletonPtr();
    Ogre::ColourValue color = r_color;

    // Normalize color values
    if (color.a > 1.0f)
        color /= 255.0f;

    // Map the tags to the color
//    m_colortags [color.getAsRGBA()] = r_tags;

    // Map the tags to the color
    pair = m_colortags.insert( std::pair< Ogre::RGBA, std::string > ( color.getAsRGBA(), r_tags ) );
    
    // Mark the map as unsaved
    m_unsaved = true;

    // Push the result into the stack again
    return std::distance( m_colortags.begin(), pair.first );
}

Ogre::ColourValue pm3::PIXELMAP::get_color( int i_id ) {
    std::map<Ogre::RGBA, std::string>::iterator it;
    Ogre::ColourValue col;
    int i = 0;

    it = m_colortags.begin();
    while( it != m_colortags.end() ) {
        // Found it?
        if (i == i_id) {
            col.setAsRGBA( it->first );
            break;
        }

        i++;
        it++;
    }

    return col;
}

bool pm3::PIXELMAP::add_startpoint( const Ogre::ViewPoint &r_vpoint ) {
    for (size_t i=0; i<m_startpoints.size(); i++) {
        if (m_startpoints [i].position == r_vpoint.position) {
            m_startpoints [i] = r_vpoint;

            return false;
        }
    }

    m_startpoints.push_back( r_vpoint );

    return true;
}

void pm3::PIXELMAP::update_room( PIXELROOM *p_room ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    std::map<PIXEL, PIXELOBJ *>::iterator it;
    int dirs [2] = { 0, 0 };
    Ogre::ColourValue col [2];
    PIXELOBJ *obj = NULL;
    PIXEL pix;

    if (!p_room)
        return;

    // Get room position
    Ogre::Vector3 v = p_room->get_grid_pos();

    if (log)
        log->report( "Pixel", "I: Room pos (%d, %d, %d).\n", (int) v.y, (int) v.x, (int) v.z );

    // Get room object
    obj = p_room->get_obj();
    if (obj) {
        // Get old pixel color
        col [0] = obj->m_color;
        // Get old room directions
        dirs [0] = obj->get_directions();
    }

    // Reset the room object (and thus force all functions to work on layer bitmap instead)
    p_room->m_object = NULL;

    // Get new pixel color
    col [1] = get_pixel( v );
    // Get new room directions
    dirs [1] = get_directions( v.y, v.x, v.z );

    if (log)
        log->report( "Pixel", "I: Room dirs %d -> %d\n", dirs [0], dirs [1] );

    // Is everything the same?
    if (col [0] == col [1] && dirs [0] == dirs [1]) {
        // No matches?
        if (log)
            log->report( "Pixel", "I: Room at (%d, %d, %d) has not changed.\n", (int) v.y, (int) v.x, (int) v.z );
        // Set the room back the way it was and return
        p_room->m_object = obj;
        return;
    }

    if (log)
        log->report( "Pixel", "I: Setup room at (%d, %d, %d).\n", (int) v.y, (int) v.x, (int) v.z );

    // Setup room
    room_setup( p_room );

    // Only color changed?
    if (dirs [0] == dirs [1]) {
        // No need for messing with portals and zones then
        if (log)
            log->report( "Pixel", "I: Room at (%d, %d, %d) has no portal changes.\n", (int) v.y, (int) v.x, (int) v.z );

        //! \todo Delete room zone if the room object has been deleted

        return;
    } else {
        if (log)
            log->report( "Pixel", "I: Setup room zone at (%d, %d, %d).\n", (int) v.y, (int) v.x, (int) v.z );

        // Setup room zone
        room_zone_setup( p_room );
    }
}

Ogre::ColourValue pm3::PIXELMAP::get_pixel( const Ogre::Vector3 &r_pos ) const {
    Ogre::ColourValue col;
    PIXELLAYER *layer = get_layer( r_pos.y );

    if (layer)
        col = layer->get_pixel( r_pos.x, r_pos.z );

    return col;
}

bool pm3::PIXELMAP::set_pixel( const Ogre::Vector3 &r_pos, const Ogre::ColourValue &r_color ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    std::map<PIXEL, PIXELOBJ *>::iterator it;
    PIXEL pix;

    if (log)
        log->report( "Pixel", "I: Setting pixel (%d, %d, %d) to (%f, %f, %f, %f)\n", 
            (int) r_pos.y, (int) r_pos.x, (int) r_pos.z,
            r_color.r, r_color.g, r_color.b, r_color.a );

    // Layer within limits
    if ( (int) r_pos.y < 0 || (int) r_pos.y >= (int) m_layers.size() || !m_layers [(int) r_pos.y])
        return false;

    // Update pixel color on the image
    m_layers [(int) r_pos.y]->set_pixel( r_pos.x, r_pos.z, r_color );

    // Update the new room
    update_room( get_room( r_pos.y, r_pos.x, r_pos.z ) );

    // Update nearby rooms
    update_room( get_room( r_pos.y + 1, r_pos.x, r_pos.z ) );
    update_room( get_room( r_pos.y - 1, r_pos.x, r_pos.z ) );
    update_room( get_room( r_pos.y, r_pos.x + 1, r_pos.z ) );
    update_room( get_room( r_pos.y, r_pos.x - 1, r_pos.z ) );
    update_room( get_room( r_pos.y, r_pos.x, r_pos.z + 1 ) );
    update_room( get_room( r_pos.y, r_pos.x, r_pos.z - 1 ) );

    // Pair room portals
    room_pair_portals( get_room( r_pos.y, r_pos.x, r_pos.z ) );
    room_pair_portals( get_room( r_pos.y + 1, r_pos.x, r_pos.z ) );
    room_pair_portals( get_room( r_pos.y - 1, r_pos.x, r_pos.z ) );
    room_pair_portals( get_room( r_pos.y, r_pos.x + 1, r_pos.z ) );
    room_pair_portals( get_room( r_pos.y, r_pos.x - 1, r_pos.z ) );
    room_pair_portals( get_room( r_pos.y, r_pos.x, r_pos.z + 1 ) );
    room_pair_portals( get_room( r_pos.y, r_pos.x, r_pos.z - 1 ) );

    return true;
}

bool pm3::PIXELMAP::remove_startpoint( const Ogre::Vector3 &r_point ) {
    for (size_t i=0; i<m_startpoints.size(); i++) {
        if (m_startpoints [i].position == r_point) {
            m_startpoints.erase( m_startpoints.begin() + i );

            return true;
        }
    }

    return false;
}

void pm3::PIXELMAP::dump() {
    LOGGER *log = LOGGER::getSingletonPtr();
    std::map<PIXEL, PIXELOBJ *>::iterator it1;
    std::map<Ogre::RGBA, std::string>::iterator it2;
    std::set<PIXELOBJ *, OBJ_COMPARE>::iterator it3;
    PIXEL pix;
    int i;

    if (log) {
        log->report( "Pixel", "I: Map %d (\"%s\") dump:\n", m_id, m_name.c_str() );
        log->report( "Pixel", "   Room size = %f\n", m_size );
        log->report( "Pixel", "   Old player grid position = (%f, %f, %f)\n", 
            m_old_player_grid_pos.x, m_old_player_grid_pos.y, m_old_player_grid_pos.z );

        // Dump layers
        log->report( "Pixel", "   Layers (%d):\n", m_layers.size() );
        for (i=0; i<(int)m_layers.size(); i++) {
            log->report( "Pixel", "      Layer %d -> 0x%X\n", i, m_layers [i] );
        }

        // Dump zones
        log->report( "Pixel", "   Zones (%d):\n", m_zones.size() );
        for (i=0; i<(int)m_zones.size(); i++) {
            if (m_zones [i])
                log->report( "Pixel", "      Zone %d = \"%s\"\n", i, m_zones [i]->getName().c_str() );
            else
                log->report( "Pixel", "      Zone %d = \"NULL\"\n", i );
        }

        // Dump objects
        log->report( "Pixel", "   Objects (%d):\n", m_objects.size() );
        it3 = m_objects.begin();
        while (it3 != m_objects.end()) {
            log->report( "Pixel", "      Object %d -> 0x%X\n", i, *it3 );
            it3++;
        }

        // Dump colormap
        log->report( "Pixel", "   Colormap (%d):\n", m_colormap.size() );
        it1 = m_colormap.begin(); i = 0;
        while (it1 != m_colormap.end()) {
            pix = it1->first;
            log->report( "Pixel", "      %d) Pixel (%f, %f, %f, %f) with directions \"%s\" -> 0x%X\n", 
                i, pix.m_color.r, pix.m_color.g, pix.m_color.b, pix.m_color.a,
                pm3::util::directions_to_str( pix.m_directions ).c_str(),
                it1->second );
            it1++; i++;
        }

        // Dump colortags
        log->report( "Pixel", "   Colortags (%d):\n", m_colortags.size() );
        it2 = m_colortags.begin(); i = 0;
        while (it2 != m_colortags.end()) {
            log->report( "Pixel", "      %d) Color 0x%X = \"%s\"\n", 
                i, it2->first, it2->second.c_str() );
            it2++; i++;
        }

        // Dump startpoints
        log->report( "Pixel", "   Startpoints (%d):\n", m_startpoints.size() );
        for (i=0; i<(int)m_startpoints.size(); i++) {
            log->report( "Pixel", "      Startpoint %d = (%f, %f, %f) (%f, %f, %f, %f)\n", 
                i, m_startpoints [i].position.x, m_startpoints [i].position.y, m_startpoints [i].position.z,
                m_startpoints [i].orientation.x, m_startpoints [i].orientation.y, m_startpoints [i].orientation.z, 
                m_startpoints [i].orientation.w );
        }
    }
}

std::string pm3::PIXELMAP::save_script( const std::string &r_path ) {
    std::map< Ogre::RGBA, std::string >::iterator it;
    std::set< PIXELOBJ *, OBJ_COMPARE >::iterator it2;
    Ogre::ColourValue col;
    std::string tags, dirs;
    size_t i;

    if (r_path.empty())
        return "";

    std::string path = get_directory() + r_path;

    FILE *fo = fopen( path.c_str(), "wt" );

    if (fo) {
        fprintf( fo, "-- PMaz3 mapscript at %s\n", get_directory().c_str() );
        fprintf( fo, "map = get_active_map();\n\n" );
        fprintf( fo, "map:set_room_size( %f );\n", m_size );

        // Write colortags
        fprintf( fo, "\n-- Colors need to be added first\n--- R, G, B, A, tags\n" );
        it = m_colortags.begin();
        while (it != m_colortags.end()) {
            // Get color
            col.setAsRGBA( it->first );
            col *= 255.0f;
            // Get tags
            tags = it->second;

            // Write it
            fprintf( fo, "map:add_color( Ogre.ColourValue( %d, %d, %d, %d ), \"%s\" );\n",
                    (int) col.r, (int) col.g, (int) col.b, (int) col.a, tags.c_str() );

            it++;
        }

        // Write objects
        fprintf( fo, "\n-- Then objects\n--- R, G, B, A, directions, objectscript\n" );
        it2 = m_objects.begin();
        while (it2 != m_objects.end()) {
            if (*it2) {
                // Get color
                col = (*it2)->get_color();
                col *= 255.0f;
                // Get directions
                dirs = pm3::util::directions_to_str( (*it2)->get_directions() );
                // Get script path
                path = pm3::util::strip_dir( (*it2)->get_script(), get_directory() );

                // Write it
                fprintf( fo, "map:add_object( Ogre.ColourValue( %d, %d, %d, %d ), \"%s\", \"%s\" );\n",
                        (int) col.r, (int) col.g, (int) col.b, (int) col.a, dirs.c_str(), 
                        path.c_str() );
            }

            it2++;
        }

        // Write layers
        fprintf( fo, "\n-- Then layers\n" );
        for (i=0; i<m_layers.size(); i++) {
            if (m_layers [i]) {
                // Get script path
                path = pm3::util::strip_dir( m_layers [i]->get_script(), get_directory() );

                // Write it
                fprintf( fo, "map:add_layer( \"%s\" );\n", path.c_str() );
            }
        }

        // Mark the script as saved
        m_unsaved = false;

        fclose( fo );

        return path;
    }

    return "";
}

void pm3::PIXELMAP::set_render_mode( pm3::RENDER_MODE e_mode ) {
    m_render_mode = e_mode;

    for (size_t i=0; i<m_layers.size(); i++) {
        if (m_layers [i])
            m_layers [i]->set_render_mode( e_mode );
    }
}

void pm3::PIXELMAP::show_debug_boxes( bool b_show ) {
    m_show_debug = b_show;

    for (size_t i=0; i<m_layers.size(); i++) {
        if (m_layers [i])
            m_layers [i]->show_debug_boxes( b_show );
    }
}

